﻿/**
 * Copyright (C) 2012-2013 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Spi;
using System.Reflection;
using NGuice.Inject.Internal.Util;

namespace NGuice.Inject.Internal
{
    //完成！
    /// <summary>
    /// C#中泛型没有通配符，使用标记接口解决此问题
    /// </summary>
    public interface ProviderMethod
    {
        Key Key
        {
            get;
        }

        MethodInfo Method
        {
            get;
        }

        object Instance
        {
            get;
        }

        void Configure(IBinder binder);
    }

    /// <summary>
    /// 调用一个方法和返回自身结果的提供者
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ProviderMethod<T> : IProviderWithDependencies<T>, ProviderMethod
    {
        private readonly Key<T> key;
        private readonly Type scopeAnnotation;
        private readonly object instance;
        private readonly MethodInfo method;
        private readonly ImmutableSet<Dependency> dependencies;
        private readonly List<IProvider> parameterProviders;
        private readonly bool exposed;

        public ProviderMethod(Key<T> key, MethodInfo method, object instance,
                              ImmutableSet<Dependency> dependencies, List<IProvider> parameterProviders,
                              Type scopeAnnotation)
        {
            this.key = key;
            this.scopeAnnotation = scopeAnnotation;
            this.instance = instance;
            this.dependencies = dependencies;
            this.method = method;
            this.parameterProviders = parameterProviders;
            this.exposed = method.GetCustomAttributes(typeof(ExposedAttribute), true).Length > 0;
        }

        public Key<T> Key
        {
            get { return key; }
        }

        Key ProviderMethod.Key
        {
            get { return Key; }
        }

        public MethodInfo Method
        {
            get { return method; }
        }

        public object Instance
        {
            get { return instance; }
        }

        public void Configure(IBinder binder)
        {
            binder = binder.WithSource(method);

            if (scopeAnnotation != null)
            {
                binder.Bind(key).ToProvider(this).In(scopeAnnotation);
            }
            else
            {
                binder.Bind(key).ToProvider(this);
            }

            if (exposed)
            {
                // the cast is safe 'cause the only binder we have implements PrivateBinder. If there's a
                // misplaced @Exposed, calling this will add an error to the binder's error queue
                ((IPrivateBinder)binder).Expose(key);
            }
        }

        public T Get()
        {
            object[] parameters = new object[parameterProviders.Count];
            for (int i = 0; i < parameters.Length; i++)
            {
                parameters[i] = parameterProviders[i].Get();
            }

            try
            {
                // We know this cast is safe becase T is the method's return type.
                T result = (T)method.Invoke(instance, parameters);
                return result;
            }
            catch (MethodAccessException e)
            {
                throw new AssertionError("method access exception", e);
            }
            catch (TargetInvocationException e)
            {
                throw Exceptions.ThrowCleanly(e);
            }
        }

        public ISet<Dependency> Dependencies
        {
            get { return dependencies; }
        }

        object IProvider.Get()
        {
            return Get();
        }

        public override string ToString()
        {
            return "@Provides " + StackFrames.ForMember(method).ToString();
        }

        public override bool Equals(object obj)
        {
            if (obj is ProviderMethod)
            {
                ProviderMethod o = (ProviderMethod)obj;
                return method.Equals(o.Method)
                   && instance.Equals(o.Instance);
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            // Avoid calling hashCode on 'instance', which is a user-object
            // that might not be expecting it.
            // (We need to call equals, so we do.  But we can avoid hashCode.)
            return Objects.GetHashCode(method);
        }

    }
}
